Displaying related table data in vuejs - javascript

I'm learning vuejs and using laravel for the backend api. I have the following tables
articles, articles_translations and i mentioned their relations in their models
i created a vue component fpr articles
<template>
<div>
<div v-for="article in articles" :key="article.articles">
<h4>{{article.translations}}</h4>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { mapGetters } from 'vuex'
export default {
layout: 'basic',
computed: mapGetters({
locale: 'lang/locale'
}),
data: function () {
return {
articles: []
}
},
mounted() {
var app = this;
console.log(this.locale);
axios.get('/api/articles')
.then(response => {
// JSON responses are automatically parsed.
this.articles = response.data
})
.catch(e => {
this.errors.push(e)
})
}
}
</script>
this displays [ { "id": 1, "article_id": 1, "title": "This is an example title", "subtitle": "this is a subtitle", "content": "this is the content", "locale": "en", "created_at": null, "updated_at": null } ] as expected
I want to display the articles in this format
article title article
article subtitle
article content
In blade i normally do {{ $article->article_translations->title }} to fetch related table data. How does this work in vue? How to display the data in the format i mentioned.

Judging by what you posted you could get the article translation attributes like this:
<div v-for="article in articles" :key="article.articles">
<div v-for="translation in article.translations">
<h4>{{ translation.title }}</h4>
{{ translation.subtitle }}
{{ translation.content }}
</div>
</div>
Basically add another for loop, since your translations are in an array. And then simply display the attributes.

just use the Laravel query builder the inner join will help to your problem this is a quick example then you will receive the collection of properties or your object.
explanation: users table get a User object, people table is related to the user with user_id column. just need to join tables related and get this result
$users = DB::table('people')
->join('users', 'users.id', '=' ,'people.user_id')
->select('users.*',
'first_name',
'last_name',
'street_address',
'city',
'state',
'contact_phone'
)->get();
return response()->json(['data' => $users], 200);

Related

Data from API doesn't show up in website but shows no error vuejs + axios

I'm learning vue js right now, and trying to get API data from this site https://reqres.in/ with axios. I want to take the data like email, name and put it into a card. It shows no error because the card is looping, but the data itself, like email and name, doesn't show up. How do I fix this?
here's is my script
data() {
return {
users: [],
};
},
methods: {
setUsers(data) {
this.users = data;
},
},
mounted() {
axios
.get("https://reqres.in/api/users")
.then((response) => this.setUsers(response.data))
.catch((error) => console.log("Gagal : ", error))
},
my card
<div class="row mb-3">
<div class="col md-2 mt-4" v-for="user in users" :key="user.id">
<card :user="user"/>
</div>
</div>
and my card components
<template>
<div>
<h2>Card API {{user.email}}</h2>
</div>
</template>
<script>
export default {
name: 'card',
props: ['user']
};
</script>
the output on my site is just 'Card API' looping for 6 times because the total data is 6 but not the email. Please help, thank you
The response.data you get is an object you fetched from the API. You need to access the "data" property of this object:
.then((response) => this.setUsers(response.data.data))

Access nested nodes with GraphQL (Nuxt)

i’m experiencing some issues with Apollo, GraphQL and Nuxt. i don’t know if it’s especially related to Nuxt though, or with vue.
i’m trying to use WordPress as headless CMS via WP-GraphQL plugin. here’s my query
WP-GraphQL interface
i basically created a graphql folder with a posts.js file inside, that contains my query
import gql from 'graphql-tag'
export const myQuery = gql`
query myQuery {
posts {
nodes {
id
title
date
slug
author {
node {
name
}
}
featuredImage {
node {
uri
sourceUrl
srcSet
}
}
}
}
}
`
then, all i need to do is to print my data in the template. here's the script part first.
<script>
import { myQuery } from '~/graphql/posts'
export default {
data() {
return {
posts: [],
}
},
apollo: {
posts: {
prefetch: true,
query: myQuery,
},
},
watch: {
async $route() {
await this.$nuxt.refresh()
window.scrollTo(0, 0)
},
},
transition: 'home',
async mounted() {
this.posts = await this.$apollo.query({ query: myQuery })
this.posts = this.posts.data.posts.nodes
this.loading = false
}
</script>
and then comes the template :
<template>
<section class="featured-projects">
<div class="featured-projects__wrapper">
<article v-for="post in posts" :key="post.id">
<p>{{ post.id }}</p>
<h2>{{ post.title }}</h2>
<span>{{ post.date }}</span>
</article>
</div>
</section>
</section>
</template>
everything just works!
now, i would like to print post author name as well. i first tried this :
<span>{{ post.author }}</span>
and this actually prints this :
{
"node": {
"name": "max max",
"__typename": "User"
},
"__typename": "NodeWithAuthorToUserConnectionEdge"
}
it totally makes sense, as author is an object with nested items in it. so according to what i’m being returned and following GraphQL API structure, to display post author name, think i should do something like this instead :
<span>{{ post.author.node.name }}</span>
and here’s the error i get, and i don’t know what to do to access what i want :
Cannot read property 'node' of undefined.
your problem arises from reading the data before it is loaded.
depending on your js settings you should be able to use one of the following:
<span>{{ post?.author.node.name }}</span>
or <span>{{ post ? post.author.node.name : '' }}</span>
according to the Vue Apollo documentation it could also be a problem with the duplication of the query
<script>
import { myQuery } from '~/graphql/posts'
export default {
data() {
return {
posts: [], // initialization
}
},
apollo: {
posts: {
prefetch: false, // to prevent SSR
query: myQuery,
update: data => {
console.log('overwrite posts with new data', data.posts)
return data.posts
}
},
}
}
</script>
further as there seem to be cases in which the author has more than one entry (perhaps co authors?) I would try to update the author rendering to the following:
<template>
<section class="featured-projects">
<div class="featured-projects__wrapper">
<article v-for="post in posts" :key="post.id">
<p>{{ post.id }}</p>
<div v-if="Array.isArray(post.author)">
first author is: {{ post.author[0].node.name }}
</div>
<div v-else-if="post.author">
author is: {{ post.author.node.name }}
</div>
<div v-else="post.author">
no author
</div>
</article>
</div>
</section>
</section>
</template>

Vue Bootstrap Pagination Define :Total-rows

I am learning to paginate data returned from an API using AXIOS. I have a working set of code, but there is a place in the code defined by bootstrap for :Total-rows, this is currently hardcoded but this creates extra rows based on the value rather than a computed value. I want to calculate the number of rows dynamically.
I know that I can count the response data from the api using: this.variable = response.data.length, but the way I am calling the data is using page variable to paginate.
Any suggestions on an efficient way to accomplish this somewhat seemingly simple call?
<template>
<div id="app">
<div class="row">
<div class="col-md-12">
<li v-for="item in todos" :key="item.id">
{{ item.name }} : {{ item.type }}
</li>
</div>
</div>
<b-pagination size="md" :total-rows="54" v-model="currentPage" :per-page="10" #input="getPostData(currentPage)">
</b-pagination>
</div>
</template>
VUE
<script>
//Import axios for REST API calls
import axios from 'axios'
import 'regenerator-runtime/runtime';
//Import bootstrap CSS
import 'bootstrap/dist/css/bootstrap.css'
//Import bootstrap vue CSS
import 'bootstrap-vue/dist/bootstrap-vue.css'
const baseURL = 'http://localhost:3000/todos?_page='+this.currentPage+'&_limit='+this.limit;
export default {
name: 'app',
data () {
return {
title: 'Vue.js Pagination Example With Bootstrap',
currentPage: 1,
limit: 5,
todos: [],
todoName: "",
todoType: "",
}
},
methods: {
// Fetches todos when the component is created.
getPostData (currentPage) {
axios.get('http://localhost:3000/todos?_page='+this.currentPage+'&_limit='+this.limit)
.then(response => {
//console.log(response)
// JSON responses are automatically parsed.
this.todos = response.data
})
.catch(e => {
this.errors.push(e)
})
},
async addTodo() {
const res = await axios.post(baseURL, {
name: this.todoName,
type: this.todoType,
});
this.todos = [...this.todos, res.data];
//resets the input field
this.todoName = "";
this.todoType = "";
},
}, //end of methods
//detects the current page on load
mounted(currentPage){
this.getPostData(currentPage)
}
}
</script>
You will need the API to return the total amount of rows, otherwise your frontend have no way of knowing how many pages to show.
You can find an example of this below, which use a dummy/testing API called reqres. This API returns various information, like the current page, total amount of rows and per page and of course the data for the requested page.
new Vue({
el: "#app",
data() {
return {
currentPage: 1,
totalRows: 0,
perPage: 0,
users: [],
request: null
}
},
methods: {
async getData(page) {
const response = await fetch(`https://reqres.in/api/users?page=${page}&per_page=3`).then(resp => resp.json())
this.perPage = response.per_page;
this.users = response.data;
this.totalRows = response.total;
// Only for testing purposes
this.request = response
}
},
created() {
this.getData(this.currentPage)
}
})
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap#4.5.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.18.1/bootstrap-vue.min.css" />
<script src="//cdn.jsdelivr.net/npm/vue#2.6.12/dist/vue.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.18.1/bootstrap-vue.min.js"></script>
<div id="app">
<b-pagination
v-model="currentPage"
:total-rows="totalRows"
:per-page="perPage"
#change="getData">
</b-pagination>
<ul>
<li v-for="{ first_name, last_name } in users">
{{ first_name }} {{ last_name }}
</li>
</ul>
Request
<pre>{{ request }}</pre>
</div>

Using a method to get array to iterate through with a V-for loop, but nothing is being displayed

I am making a personal project to learn how Vue.js and Express communicate together. I managed to get the desired data from the server by passing a prop in the Vue template (brand of watch to be fetched from the server) as an argument to a method that fetches the data.
This method is being called in the V-for declaration and returns an array of objects. Now, when I test this with a standard array that I instantiated inside the data function all is well, and the data displays just fine. The method definitely fetches the data because I can see it inside Vue devtools and when I print it to the console. So it is there, but for some reason it doesn't want to play nice with V-for.
Here's the code:
<template>
<div class="wrapper">
<div class="product" v-for="(product, index) in getProductsByBrand(brand)" :key="index">
<div class="product-name">
{{ product }}
</div>
</div>
</div>
</template>
<script>
import ProductService from '#/services/ProductService'
export default {
name: 'product',
props: [
'brand'
],
data () {
return {
products: [],
shoppingItems: [
{ name: 'apple', price: '7' },
{ name: 'orange', price: '12' }
]
}
},
methods: {
async getProductsByBrand (brand) {
const response = await ProductService.fetchProductsByBrand(brand)
this.products = response.data.product
console.log(this.products)
console.log(this.shoppingItems)
return this.products
}
When I replace the getProductsByBrand() method with the shoppingItems array it all works. That method returns another array, so I don't see why V-for is having trouble with displaying that data.
Any help is much appreciated!
Vue templates are rendered by a render function, and it's a normal sync function. Therefore you can't call an async function in the template. A better pattern for your code would be as follows:
Template:
<div class="product" v-for="(product, index) in products" :key="index">
Script:
methods: {
async getProductsByBrand (brand) {
const response = await ProductService.fetchProductsByBrand(brand)
return response.data.product
}
},
async created() {
this.products = await this.getProductsByBrand(this.brand);
}
It amounts to the same thing but loads the data into the products data property during the created lifecycle hook.

Vuex state and vue-router

I'm trying to make a blog with vuejs and I'm a bit stuck.
All my article data is in a Vuex Store like this :
export const store = new Vuex.Store({
state: {
articles: [{
title: "Article 1",
id: 1,
content:"Article 1 content"
}, {
title: "Article 2",
id: 2,
content:"Article 2 content"
}
}]
}
I have a grid of articles on my homepage :
<div class="item-article" v-for="article in articles">
<router-link :to="{path: '/article/'+article.id}"></router-link>
<div>{{ article.title }}</div>
</div>
When I click on a grid article, I want it to redirect to the articlePage.vue component with the data of the same id article.
So far, on my articlePage.vue component I am with this :
<div v-for="article in selectedArticle">
<h1>{{ article.title }}</h1>
<p>{{ article.content }}</p>
</div>
computed: {
selectedArticle(){
return this.$store.state.articles.filter(article => article.id == this.$route.params.id);
}
}
I want to use $route.params.id in order to catch the matching id in VueX, and access to the the right data. But it's not working. What am I doing wrong?
Thanks for your help! :)
First, define your routes and look how to create a dynamic route:
const routes = [
{
path: '/articles/:id',
name: 'articles',
component: articlePage,
props: true
}
]
In your Vue instace, pass routes and vuex store:
new Vue({
store,
router: routes,
el: '#app',
render: h => h(App)
})
In getters property in your Vuex Store, you need to create a method that filter/find article by id, something like that:
getArticlesById: (state) => (id) => state.articles.find(article => article.id === id)
And finally, in your mounted() method, call him:
this.article = this.$store.getters.getArticlesById(this.articleId)
this.articleId is the param sending by URL, remember define him in component props:
export default {
name: "articlePage",
props: ["category"],
...}
Name your routes and pass your articleId like this:
<router-link :to="{ name: 'article', params: { id: article.id }}">{{article.title}}</router-link>
Also, using Array.prototype.find might be a better idea than using Array.prototype.filter because the second one would give you a one-element array in your case.
You should use find instead of filter, and add return within the find callback function
selectedArticle() {
let article = this.$store.state.articles.find(article => {
return article.id == this.$route.params.id
});
return article;
}

Categories

Resources