Laravel 5: show comments using Vue.js - javascript

I am creating blog commenting system, I want to show comments for a post using vue.js.
In console, it says
Property or method "comment" is not defined on the instance but
referenced during render.
Also, when I try to catch user name, I got this error
Error in render: "TypeError: Cannot read property 'user' of undefined"
I want to show comments and users who commented to a particular post
in show.blade.php.
web.php
Route::get('results/{post}', 'ResultsController#show')->name('posts.show');
ResultsController
public function show(Post $post)
{
$recommended_posts = Post::latest()
->whereDate('date','>',date('Y-m-d'))
->where('category_id','=',$post->category_id)
->where('id','!=',$post->id)
->limit(7)
->get();
$posts['particular_post'] = $post;
$posts['recommended_posts'] = $recommended_posts;
//return $post->comments()->paginate(5); it returns objects
return view('posts.show',compact('posts'));
}
Comments.vue
<div class="reply-comment" :v-for="comment in comments">
<div class="user-comment" >
<div class="user">
<!--<img src="" alt="" >-->
<avatar :username="comment.user.name" :size="30" ></avatar>
</div>
<div class="user-name">
<span class="comment-name">{{ comment.user.name }}</span>
<p> {{ comment.body }} </p>
</div>
</div>
<div class="reply">
<div class="seemorecomments">
see more
</div>
<button class="reply-button">
<i class="fas fa-reply"></i>
</button>
</div>
</div>
<script>
import Avatar from 'vue-avatar'
export default {
props: ['post'],
components: {
Avatar
},
mounted() {
this.fetchComments()
},
data: () => ({
comments: {
data: []
}
}),
methods: {
fetchComments() {
axios.get(`/results/${this.post.id}`).then(({ data }) => {
this.comments = data
})
}
}
}
show.blade.php
<comments-component :post="{{ $posts['particular_post']->comments }}"></comments-component>
migration table
Schema::create('comments', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('user_id');
$table->integer('post_id');
$table->text('body');
$table->integer('comment_id')->nullable();
$table->timestamps();
});
comment.php, I have this.
protected $with = ['user'];

You have a couple of minor issues with your Vue file that can be addressed pretty quickly.
First, you should define comments as an empty array — a collection will be returned as an array of objects to the Vue. By adding an unnecessary data property in the beginning, you are allowing the v-for loop to run in your template before the data has been retrieved.
EDIT: I'm not sure about the way you wrote this data function, so I have re-written it a way in which I'm familiar.
data() {
return {
comments: []
}
},
Second, you want to get the correct data from the response. Axios data is stored another level deep (response.data). Of course, if you are paginating the results, they are one more level deep (response.data.data).
fetchComments() {
axios.get(`/results/${this.post.id}`).then(response => {
this.comments = response.data
// or for paginated results
// this.comments = response.data.data
})
}
EDIT: Thank you for providing the Gist! I think I'm seeing things more clearly now.
Update your controller like so:
You want to load the comments into the post here.
public function show(Post $post)
{
$recommended_posts = Post::latest()
->whereDate('date','>',date('Y-m-d'))
->where('category_id','=',$post->category_id)
->where('id','!=',$post->id)
->limit(7)
->get();
// load the post comments here
$post->load('comments');
$posts['particular_post'] = $post;
$posts['recommended_posts'] = $recommended_posts;
return view('posts.show',compact('posts'));
}
And you blade like so:
Your module wants a single post, not an array of comments.
<comments-component :post="{{ $posts['particular_post'] }}"></comments-component>
And you Vue like so:
You don't actually need to use Axios at all since we've already loaded the comments.
<script>
import Avatar from 'vue-avatar'
export default {
props: ['post'],
components: {
Avatar
},
data() {
return {
comments: this.post.comments
}
},
}
</script>

Related

How to load locally stored images conditionally in Vuejs?

<template>
<div>
<div v-if="item">
<h1>Price: {{ item.email }}</h1>
<v-if item.email=="john#gmail.com">
<img :src="../"/>
</v-if>
</div>
</div>
</template>
<script>
import { routerid } from "./routerid";
export default {
name: "User",
components: {},
data() {
return {
item: [],
};
},
mounted() {
this.loadData();
},
computed: {
routeId() {
return this.$route.params.id;
},
},
watch: {
routeId() {
console.log("Reload (route change)");
this.loadData();
},
},
methods: {
loadData() {
console.log("Reloading, ID", this.routeId);
if (!this.routeId) return;
routerid(this.$route.params.id).then((item) => {
this.item = item.data;
});
},
},
};
</script>
How to load locally stored images conditionally in Vuejs?
I have three images stored inside of my assets folder. as below.
Now in frontend i want to call them conditionally like for example (if ---> john#gmail.com show the john image stored inside of assets folder. Else no. Similarly for other images too. Like derk and Kate...
Do i need to write if else condition. To load the images based on email? Or any other way to do that?
Code:- https://codesandbox.io/s/combined-logic-api-forked-nzzzwc?file=/src/components/User.vue
api response:- https://fakestoreapi.com/users/1
{"address":{"geolocation":{"lat":"-37.3159","long":"81.1496"},"city":"kilcoole","street":"new road","number":7682,"zipcode":"12926-3874"},"id":1,"email":"john#gmail.com","username":"johnd","password":"m38rmF$","name":{"firstname":"john","lastname":"doe"},"phone":"1-570-236-7033","__v":0}
Note:- email will be different for each id.
First. I think you should provide image link on your API Response (so you should add image link column on your database table). Or if you insist, based on your case, you can use this:
<div v-if="item">
<h1>Price: {{ item.email }}</h1>
<img :src="getPic(item.email)"/>
</div>
and in your method:
getPic(src) {
return "#/assets/" + src.substring(0, src.lastIndexOf("#")) + ".png"
}
But I still really think you should store iamge link in your database

How would I iterate through pokeAPI to get all subsequent pokemon data?

Hi I'm using Vuejs to get some pokemon data. So I figured out how to retrieve all the pokemon name and their api urls to get more information about them. The issue is I don't know how to take those URLs and access each pokemon's specific data. I tried to increment a variable and concatenate it to the URL to get their data but it didn't work. I also tried to access the data from the api call I already but that also didn't work.
<template>
<div>
<h2>{{subtitle}}</h2>
<div v-for="pokemon in basicInfo" v-bind:key="pokemon.name">
<span>{{ pokemon.name}}</span>
</div>
<!-- Nothing is produced, and I dont get I an error -->
<div v-for="pokemon2 in advInfo" v-bind:key="pokemon2.index">
<span>{{pokemon2}}</span>
</div>
<script>
import axios from "axios";
export default {
data() {
return {
subtitle: "First 150 pokemon",
basicInfo: [],
advInfo:[],
i:0
};
},
methods: {
// trying to increment i
getNext: function(){
this.i=i++;
}
},
mounted() {
axios
// this gets a list of the first 20 pokemon. I can get the pokemon's name and their url
.get("https://pokeapi.co/api/v2/pokemon/")
.then(response => {
this.basicInfo = response.data.results;
});
// here I'm trying to access more specific data on each pokemon by concatenating a number to the url
axios
.get("https://pokeapi.co/api/v2/pokemon/5")
.then(response => {
this.advInfo= response.data.results;
});
}
};
</script>
<style scoped>
</style>
It looks like ".../api/v2/pokemon/" produces an object with a results array, and those results contain uri's like ".../api/v2/pokemon/(some id)"
The way to combine them is as follows:
axios.get("https://pokeapi.co/api/v2/pokemon/").then(response => {
this.basicInfo = response
let promises = this.basicInfo.map(result => {
return axios.get(result.url)
})
Promise.all(promises).then(response => {
this.advInfo = response
})
});
Now advInfo will be an array, like you expect so you can render it with v-for....
<div v-for="(pokemon2, i) in advInfo" :key="i">
<pre>{{pokemon2}}</pre>
</div>

Vuejs page loads before axios request returns data

Problem
Im trying to load data from my database using vuejs + axios in a Laravel project. Locally I can make the request using axios and it returns the data, assigns it to variables and then outputs the data to the DOM.
When I push the same code to production the axios request takes a little longer to execute but it seems to be long enough that vuejs is ready before all the data is returned, leaving me with an annoying 'undefined property' error when the data attempts to be displayed. Sometimes this error persist's through 20+ refreshes before the data loads.
What I think the issue is
So my guess from reading up is that axios isn't getting the data as quickly as it should do so vuejs is ready to start serving the data but axios hasn't had a chance to collect it yet, resulting in the undefined error
What i've read and tried
I've read that using v-if statements on the objects that depend on the data should fix this issue. But it doesn't, all it does it hide the object from view if the data is not present. For Example...
HTML
<!-- If user.name exists, display user.name -->
<div v-if="user.name">
#{{ user.name }}
</div>
JAVASCRIPT
<script>
new Vue({
el: '#container',
data: {
user: {}
},
mounted() {
this.getUserData();
},
methods: {
getUserData(){
axios.post('/user/get').then((response) => {
// Check the response was a success
if(response.data.status === 'success')
{
this.user = response.data.user;
}
});
},
}
})
</script>
This doesn't work, it just doesn't display anything when the data hasn't been loaded yet.
Question
How do I ensure that my loaded data is displayed and not return an undefined error? The only way I can think is by allowing the user to 'click to retrieve data' on a failed attempt.
Further Information
Im not using vue templates, the child/parent structure or any vue libraries. I'm importing vue.js via CDN and using it on the page in a script as seen above. Im not sure if using it like this will cause any such limitations, i've only learnt vue.js on a basic and this works for the company and the boss...
You could add a 'loaded' boolean attribute to resolve this. For example:
<script>
new Vue({
el: '#container',
data: {
isLoaded: false,
user: {}
},
mounted() {
this.getUserData();
},
methods: {
getUserData(){
axios.post('/user/get').then((response) => {
this.isLoaded = true;
// Check the response was a success
if(response.data.status === 'success')
{
this.user = response.data.user;
}
});
},
}
})
</script>
Then wrap your html to check if the data has been loaded:
<div v-if="isLoaded">
<!-- If user.name exists, display user.name -->
<div v-if="user.name">
#{{ user.name }}
</div>
</div>
You could then also display a loader:
<div v-if="isLoaded">
<!-- If user.name exists, display user.name -->
<div v-if="user.name">
#{{ user.name }}
</div>
</div>
<div v-else>
Loading...
</div>
Edited answer, to explain better what I mean.
Your code should work, no matter if it takes 1 sec or 10 sec for axios to fetch the data. You shouldn't have to check with v-if on user.name. VueJS has reactivity and upates the view if the data property changes.
See this code example.
https://codepen.io/bergur/pen/VOXrzR
Here I wait 10 seconds to populate the user object
{{ user.name }} shows nothing and then Bergur
{{ computedName }} shows undefined Hallgrímsson because user.name is undefined for 10 seconds.
{{ user.address.country }} will throw an error (Cannot read property 'country' of undefined) because user.address isn't available in the initial data object.
So one solution is to define address initially in the data object
user: {
address: {}
}
My point is that your initial code should work (when rendering). The undefined errors come when
a) You're using that name property in your vue app, e.g user.name when there is no name property.
or
b) When you're rendering 2nd level property, e.g user.address.country because address hasn't been initially defined.
With the help of lodash's debounce you can delay the time between when a method function or computed property is called and excecuted.
Also you can differentiate the delay time for the same method creating different functions eg. one being triggered by mounted and the other one by a watcher. In my case the function being called on mounted, needs to wait before the axios call returns a response and sets store.state.TableDataInit. In the watcher case it can run immediately because the store.state variable already has been set, there is no need to wait for an axios response anymore. For example:
created: function() {
this.setInitRange = _.debounce(this.setRange, 600);
this.setInitBoundaries = _.debounce(this.setBoundaries, 600);
this.setWatchRange = this.setRange;
this.setWatchBoundaries = this.setBoundaries;
},
mounted() {
this.setInitRange(this.selected);
this.setInitBoundaries();
},
watch: {
selected() {
store.commit("storedMetric", this.selected);
this.setWatchRange(this.selected);
this.setWatchBoundaries();
}
},
methods: {
setRange(value) {
var metric = value;
var dataArray = store.state.TableDataInit;
const maxMetric = dataArray.reduce(function(prev, current) {
return _.get(prev, metric) > _.get(current, metric) ? prev : current;
})[metric];
const minMetric = dataArray.reduce(function(prev, current) {
return _.get(prev, metric) < _.get(current, metric) ? prev : current;
})[metric];
this.range = [minMetric, maxMetric];
store.commit("storedOutBoundRange", this.range);
},
setBoundaries() {
this.min = store.state.OutBoundRange[0];
this.max = store.state.OutBoundRange[1];
},
<div v-if="user">
<div>
#{{ user.name }}
</div>
<div>
#{{ user.second_name }}
</div>
<div>
#{{ user.phone }}
</div>
<div>
#{{ user.any }}
</div>
</div>

Vue - Return Promise in v-for loop

I'm having a problem with a Vue JS. I'm looping over some "posts" from the WordPress Rest API, and I have to print the author of the post inside the v-for loop, for each post. The author assigned to a post, is only an ID, so I have to make a new call to the API, to get the author name based on the returned ID.
The issue i'm running into, is that when i call the function that's fetching the Author Name, it's constantly switching back and forth between all the users assigned to the posts in the posts object.
Vue.component('blog-posts', {
props: {
posts: {},
},
data() {
return {
userName: '',
}
},
template: `
<section class="entry" v-if="posts.length">
<div class="entry-content">
<article v-for="(post, index) in posts" :key="post.index">
<h1><a :href="post.link">{{ post.title.rendered }}</a></h1>
<p v-html="post.content.rendered"></p>
// THIS IS THE PROBLEM
<p v-if="getAuthor(post.author)">{{ userName }}</p>
<button type="submit" #click="deletePost(post)">Slet indlæg</button>
</article>
</div>
</section>
`,
created() {
// Of course it works if i put a constant user id in, but i need to parse in inside the loop.
// this.getAuthor(2);
},
computed: {
},
methods: {
deletePost: function(post) {
let path = new wp.api.collections.Posts({
id: post.id
});
Event.$emit('delete-post', path, post.id);
},
getAuthor(author) {
let vm = this;
let user = new wp.api.models.User({
id: author,
});
return user.fetch().then(response => {
vm.userName = response.name;
// return true;
});
},
// showAuthor(author) {
// let user = new wp.api.models.User({
// id: author
// });
// user.fetch().then(response => {
// this.userName = response.name;
// // return true;
// });
// },
},
});
const app = new Vue({
el: '#app',
data: {
wp: wp.api,
message: 'Hello Vue',
posts: [],
},
mounted() {
let posts = new wp.api.collections.Posts();
let response = posts.fetch().then(response => {
this.posts = response;
});
Event.$on('post-added', (item) => {
this.posts.unshift(item.attributes);
});
// Delete post
Event.$on('delete-post', (item, id) => {
this.posts.forEach((post, index) => {
if ( post.id === id ) {
this.posts.splice(index, 1);
item.at(0).destroy();
}
});
});
},
});
I'm not entirely sure this is the best way to return the value of a function call.
Your issue is that your blog-posts component contains multiple blog posts, but they all share a single reference to the username. As the blog posts may be authored by different people, you will of course overwrite the username multiple times as you iterate through individual posts.
The best solution is actually to abstract individual blog post into its own atomic component, e.g., your <blog-posts> component is simply a container for multiple <blog-post>: schematically it should look like this:
└─ <blog-posts>
├─ <blog-post>
├─ <blog-post>
├─ <blog-post>
├─ <blog-post>
└─ <blog-post>
You can use :post="post" so that you pass the entire post data into your atomic component:
// The `<blog-posts>` component is only a COLLECTION of `<blog-post>`
// So it is a "dumb" component in a sense
Vue.component('blog-posts', {
props: {
posts: {},
},
template: `
<section class="entry" v-if="posts.length">
<div class="entry-content">
<blog-post v-for="(post, index) in posts" :key="post.index" :post="post" />
</div>
</section>
`
});
All post-related logic should be removed from the <blog-posts> collection component and be moved into the atomic <blog-post> component instead. Then, in your new <blog-post> component, you can:
Handle all the templating needs of an individual post
Handle individual post actions, such as post deletion
Most importantly: use the mounted() lifecycle hook to perform the API call to fetch the username pertaining to that individual post
So, your atomic component code will look a bit like this:
// The `<blog-post>` component is atomic
// It contains all the information related to a SINGLE post
Vue.component('blog-post', {
props: {
post: {},
},
data() {
// Each individual blog post stores its own username
return {
userName: '';
}
},
template: `
<article>
<h1><a :href="post.link">{{ post.title.rendered }}</a></h1>
<p v-html="post.content.rendered"></p>
<p>{{ userName }}</p>
<button type="submit" #click="deletePost(post)">Slet indlæg</button>
</article>
`,
methods: {
deletePost: function(post) {
let path = new wp.api.collections.Posts({
id: post.id
});
Event.$emit('delete-post', path, post.id);
}
},
mounted: function() {
// Fetch post author metadata when the `<blog-post>` component is mounted
let user = new wp.api.models.User({
id: author,
});
user.fetch().then(responsve => this.userName = response.name);
}
});
Everything on the display side should be reactive. You should not be calling a method, any method, in the v-if of a template. You don't know/shouldn't care when the template is rendered.
Try defining a data to hold your author details, then calling getAuthor() from a created hook.

Parsing input properties in a vue component

I feel like I am running out of ideas on how to solve this issue.
So I have a component that should read a file and display some data from that file. I want to pass only the filename to component so that it can handle reading and parsing the file. To do this I added a property to the component.
The issue I seem to have is that I can't really access that property from the data function, and if I add a watcher on the property I can parse the file as expected, but I can't seem to get that data into the DOM.
This is what I have right now:
<template>
<main :key="fileName">
fileName: {{fileName}}
<div class="post">{{data}}</div>
<div class="info">
<div v-for="item in info" v-bind:key="item.name">{{item.name}}</div>
</div>
</main>
</template>
<script>
const { parse } = require("#/service/parser");
const fs = require("fs");
let postInfo = { data: "abc", info: [] };
export default {
props: ["fileName"],
watch: {
fileName: {
immediate: true,
handler: (newVal, oldVal) => {
if (newVal) {
postInfo = parse(
fs
.readFileSync(__dirname + "/../../assets/" + newVal, "utf8")
.split("\n")
);
}
}
}
},
data: () => {
return postInfo;
}
};
</script>
I am obviously completely new to Vue, and I'm probably missing something stupid here.
So what am I doing wrong and how do I get the parsed data into my DOM?
Don't use an arrow function for your data function. Arrow functions bind this to whatever context the function is declared in. You need to let Vue properly bind this to the instance it is creating. So use
data() {
return postInfo;
}
or if for some reason you need to be old school:
data: function () {
return postInfo;
}

Categories