Not returning value in vue function - vue.js

I'm using vue and I'm trying to return a user's email. I have 2 separate functions and one gets all the products while the other one
is supposed to get the user emails that is attached to that product.
The issue I'm having is that the emails isn't showing up and I'm getting an undefined as well.
Here is my code
<template>
<div class="card" style="width: 100%;">
<div class="card-body">
<table class="table">
<thead>
<tr>
<th scope="col">Email</th>
<th scope="col">Product</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products">
<td>
{{ getProductUser(product.user_id) }}
</td>
<td>
{{ product.action }}
</td>
<td>
{{ product.options }}
</td>
<td>
{{ product.created_at }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
props:['id', 'user', 'product'],
data(){
return {
products: [],
}
},
methods: {
getProducts(){
axios.get('/api/products/'+this.id+'/product').then(response => {
this.products = response.data.products;
});
},
getProductUser(user){
axios.get('/api/product/'+user).then(response => {
return response.data.email;
});
}
},
mounted() {
this.getProducts();
}
}
</script>

Apart from the fact that product.user_id is undefined as stated in the comment, you have another issue.
getProductUser doesn't return an email. It returns a promise that will provide an email.
Simplest solution would be
<td>
{{ product.userEmail }}
</td>
(which is by the way an equivalent to <td v-text="product.userEmail"/>, vue templates can be much cleaner than standard HTML).
getProducts(){
axios.get('/api/products/'+this.id+'/product').then(response => {
this.products = response.data.products;
this.products.forEach(product => {
axios.get('/api/product/'+user).then(response => {
product.userEmail = response.data.email;
});
})
});
This should work, but keep in mind that executing additional request per product is generally a bad idea, the performance would be much better if /api/products/{id}/product would have userEmail property already initialized.

Related

Not able to remove a user from a table

I have a nested table where I'm grouping users by the department that they belong to. The issue I'm having is that if I click on the delete button, that specific user that I've deleted isn't being removed from the list and I'm not getting any errors in my console.
Here is my code
<template>
<div>
<div class="row">
<div class="col-lg-12">
<table class="table table-bordered table-sm">
<thead>
<tr>
<th>Name</th>
<th>Job Title</th>
</tr>
</thead>
<tbody v-for="(users, department) in personnal">
<tr>
<td>{{ department }}</td>
</tr>
<tr v-for="user in users">
<td>{{ user.name }}</td>
<td>{{ user.job_title }}</td>
<td>
<div class="btn btn-danger btn-sm" #click="removeUser(user)">
<i class="fa fa-trash"></i>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
export default
{
props: [],
data() {
return {
personnal: {},
}
},
computed: {
},
methods: {
getUsers(){
axios.get(`/api/users`).then(response => {
this.personnal = response.data.users;
});
},
removeUser(user){
axios.delete(`/api/users/${user.id}/delete`).then(response => {
Object.keys(this.personnal).forEach(user => {
this.personnal[user].filter(u => u.id !== response.data.id);
this.personnal[user].slice(response.data);
});
});
}
},
mounted() {
this.getUsers();
}
}
</script>
First of all, you have to pass both department and user in the click event:
<div class="btn btn-danger btn-sm" #click="removeUser(department, user)">
<i class="fa fa-trash"></i>
</div>
Then, in the removeUser method:
removeUser(department, user){
axios.delete(`/api/users/${user.id}/delete`).then(response => {
const index = this.personnal[department].findIndex(u => u.id === user.id);
this.personnal[department].splice(index, 1);
});
}

Can't add new item using v-model in Vue JS

I am learning Vue.
Now, I am trying to add data with the price and finally, it calculates total price:
Here is the HTML
<div id="app">
<form #submit.prevent="addItem">
<table border="1" cellpadding="10" width="300">
<tr>
<td colspan="2"><strong>Add New Item</strong></td>
</tr>
<tr>
<td>
<input type="text" name="" v-model="newItem" placeholder="Item Name">
</td>
<td>
<input type="number" name="" v-model="newItemPrice" placeholder="Item Price">
</td>
</tr>
</table>
</form>
<br>
<table border="1" cellpadding="10" width="400">
<tr>
<th>Item Name</th>
<th>Item Price</th>
</tr>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.name }}</td>
<td><input type="number" name="" v-model="item.price"></td>
<td><button #click="removeItem(index)">X</button></td>
</tr>
<tr>
<td>Total</td>
<td><strong>{{ total }}</strong></td>
</tr>
</table>
</div>
Here is the Vue Instance:
new Vue({
el : '#app',
data : {
items: [
{ name: 'Rice', price : 12.60 },
{ name: 'Oil', price : 22.00 },
{ name: 'Mango', price : 32.50 },
{ name: 'Orange', price : 42.00 },
],
newItem : '',
newItemPrice : '',
},
computed: {
total() {
var total = 0;
this.items.forEach( item => {
total += parseFloat( item.price );
})
return total;
}
},
methods: {
addItem() {
this.items.push({
name: this.newItem,
price: 0
});
},
removeItem( index ) {
this.items.splice( index, 1 )
}
}
});
You can see it's by default showing item name and price. I want to add new item using the v-model called newItem But It's not adding the new item to the table
BUT
If I remove the Item Price column I mean this line:
<td>
<input type="number" name="" v-model="newItemPrice" placeholder="Item Price">
</td>
then it's adding the new item perfectly :(
can you tell me what's wrong here?
See two issues with the fiddle:
There is no way to submit the form data
When pushing the price field was not added to the object
After fixing both of them it works well in this fiddle.
This happens because of browser implementation. As mentioned in W3C Specs:
When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.
But in case of multiple elements, the enter keypress does not trigger the form submit and thus you get this behaviour.
To resolve this you can simply use #keyup.enter.prevent="addItem" to listen to the enter keypress on each input and call the addItem() function like:
new Vue({
el: '#app',
data: {
items: [{name:"Rice",price:12.6},{name:"Oil",price:22},{name:"Mango",price:32.5},{name:"Orange",price:42}],
newItem: '',
newItemPrice: null,
},
computed: {
total() {
var total = 0;
this.items.forEach(item => {
total += parseFloat(item.price);
})
return total;
}
},
methods: {
addItem() {
this.items.push({
name: this.newItem,
price: 0
});
},
removeItem(index) {
this.items.splice(index, 1)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<div id="app">
<form #submit.prevent="addItem">
<table border="1" cellpadding="10" width="300">
<tr>
<td colspan="2"><strong>Add New Item</strong></td>
</tr>
<tr>
<td>
<input type="text" name="" v-model="newItem" placeholder="Item Name"
#keyup.enter.prevent="addItem">
</td>
<td>
<input type="number" name="" v-model="newItemPrice" placeholder="Item Price"
#keyup.enter.prevent="addItem">
</td>
</tr>
</table>
</form>
<br>
<table border="1" cellpadding="10" width="400">
<tr>
<th>Item Name</th>
<th>Item Price</th>
</tr>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.name }}</td>
<td><input type="number" name="" v-model="item.price"></td>
<td><button #click="removeItem(index)">X</button></td>
</tr>
<tr>
<td>Total</td>
<td><strong>{{ total }}</strong></td>
</tr>
</table>
</div>
You should put a new line in your form, my suggestion is to put just above the close form tag </form>:
<input type="submit" value="add">
Another fix to do is in your methods addItem()
addItem() {
this.items.push({
name: this.newItem,
price: this.newItemPrice
});
}
Where it is the number 0 you should provide the this.newItemPrice to it work properly.

Passing b-icon to <td> element in VueJS

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>

How to extract single record ( id ) from JSON response and display in DOM using VueJS?

I have created a List view to see records from a JSON API response. Now I want user to click on a icon eg. '>' to see only that record on the full page ( Detail View ). I am not sure which vue directive I need to use and how to display in the DOM.
I tried this http://localhost:8000/Patients/1/?format=json but then directive v-for is not working
<template>
<div id="app" class="container">
<p v-if="loading">Loading...</p>
<div v-else>
<h3 class="heading" style="text-align:left">Patients List</h3>
<input id="lens" v-model= "search" placeholder ="Search here">
<br></br>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">Mobile</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
<tr v-for="patient in filteredPatients" v-bind:key="patient">
<td>{{ patient.id }}</td>
<td>{{ patient.first_name + " " + patient.last_name }}</td>
<td>{{ patient.mobile }}</td>
<td>{{ patient.email }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: 'app',
data () {
return {
loading: false,
patients: '',
search: '',
}
},
mounted () {
this.loading = true;
axios
.get('http://localhost:8000/Patients/?format=json')
.then(response => (this.patients = response.data))
.catch(error => console.log(error))
.finally(() => this.loading = false)
},
computed: {
filteredPatients() {
return this.patients.filter(patient => {
return `${patient.first_name} ${patient.last_name} ${patient.email} ${patient.mobile} ${patient.id}`.includes(this.search);
})
}
From the filteredPatients()method you are only returning three field i.e first_name,last_name and email but from your loop you are expecting to get five fields i.e id,first_name,last_name,email and mobile
How about you get your data directly from the patients value in the data section but you will first have to change it to an array.
After that have an onclick listener on the table rows and pass the specific product as an argument to the listener method.This way you will have the selected/clicked record.From here you can pass the record to the other page as props.
So the final code would look somehow like this:
<div id="app" class="container">
<p v-if="loading">Loading...</p>
<div v-else>
<h3 class="heading" style="text-align:left">Patients List</h3>
<input id="lens" v-model= "search" placeholder ="Search here">
<br></br>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">Mobile</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
<tr v-for="(patient,id )in patients" :key="id" #click="getOneRecord(patient)">
<td>{{ patient.id }}</td>
<td>{{ patient.first_name + " " + patient.last_name }}</td>
<td>{{ patient.mobile }}</td>
<td>{{ patient.email }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: 'app',
data () {
return {
loading: false,
patients: {},
search: '',
}
},
mounted () {
this.loading = true;
axios
.get('http://localhost:8000/Patients/?format=json')
.then(response => (this.patients = response.data))
.catch(error => console.log(error))
.finally(() => this.loading = false)
},
computed: {
filteredPatients() {
return this.patients.filter(patient => {
return `${patient.first_name} ${patient.last_name} ${patient.email} ${patient.mobile} ${patient.id}`.includes(this.search);
})
},
methods:{
getOneRecord(record):{
}
}

Getting data from Laravel Api to Vue Page

I'm a beginner in vue.js and I'm trying to get an api result and show it to my vue page. my Api is built with Laravel 5.7.
I've just installed Vue axios package to work with http.
Here is my code:
TaskController
public function index()
{
return response(Task::all()->jsonSerialize(), Response::HTTP_OK);
}
App.vue
<template>
<div class="app-component">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Task Title</th>
<th>Priority</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<task-component v-for="task in tasks" :key="task.id" :task="task"></task-component>
<tr>
<td><input type="text" id="task" class="form-control"></td>
<td>
<select id="select" class="form-control">
<option>Low</option>
<option>Medium</option>
<option>High</option>
</select>
</td>
<td><button class="btn btn-primary">Add</button></td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import TaskComponent from './Task.vue';
export default{
data(){
return{
tasks: [],
}
},
methods: {
getTasks(){
window.axios.get('/api/tasks').then(({data})=>{
data.forEach(task =>{
this.tasks.push(task)
});
});
},
created(){
this.getTasks();
}
},
components:{TaskComponent}
}
</script>
Task Page
<template>
<tr>
<td>{{ task.id }}</td>
<td>{{ task.title }}</td>
<td>{{ task.priority }}</td>
<td><button class="btn btn-danger">Remove</button></td>
</tr>
</template>
<script>
export default{
data(){
return{
}
},
props: ['task']
}
</script>
I got no errors but no result appeared to my vue although the controller returns json data correctly
the created() hook should not be in methods:
export default {
methods: {},
created() {}
}