How can I dynamically change Axios request on button click? - javascript

I'm trying to dynamically change the req attribute of my axios request on button click.
I'm using SWAPI, then when I call get "people/", it returns "next" as the link for the next page.
When I click the button, I want to change the request to "next" attribute retrieved from the api.
<template>
<div class="container">
<ul>
<li v-bind:key="person.birth_year" v-for="person of people" class="response">
<div class="card">
<h1>{{person.name}}</h1>
<h3>Altura: {{person.height}}</h3>
<h3>Peso: {{person.mass}}</h3>
<h3>Gênero: {{person.gender}}</h3>
<h3>Cor da pele: {{person.skin_color}}</h3>
</div>
</li>
<button v-on:click="req=next">Proxima Pagina</button>
</ul>
</div>
</template>
<script>
import api from '../services/api'
import '../css/index.css'
export default {
data(){
return {
people:[],
next:"",
req:""
}
},
mounted() {
let req="people/"
api.get(req)
.then(response=>{
this.people=response.data.results;
this.next=response.data.next;
});
},
}
</script>

Use the same variable to set the new link as below :
<template>
<div class="container">
<ul>
<li v-bind:key="person.birth_year" v-for="person of people" class="response">
<div class="card">
<h1>{{person.name}}</h1>
<h3>Altura: {{person.height}}</h3>
<h3>Peso: {{person.mass}}</h3>
<h3>Gênero: {{person.gender}}</h3>
<h3>Cor da pele: {{person.skin_color}}</h3>
</div>
</li>
<button v-on:click="getData()">Proxima Pagina</button>
</ul>
</div>
</template>
<script>
import api from '../services/api'
import '../css/index.css'
export default {
data() {
return {
people: [],
next: "",
req: "people/"
}
},
mounted() {
this.getData();
},
methods: {
getData() {
api.get(this.req).then(response => {
this.people = response.data.results;
this.req = response.data.next;
});
}
}
}
</script>

Related

Vuejs emit not working form child to parent

I'm working on this app and the idea is to show details of the cars in a sidebar on click. There are several issues like the sidebar is showing four times and I resolve it somehow but I don't know why is it showing four times. now I don't getting any response on emit call help me out please, I try $parent.$emit, $root.$emit but not seems working!!!
<template>
<div class="home">
<!-- warehouse details -->
<div
v-for="(detail, detailindex) in details"
:key="detailindex"
class="container mt-5 mb-5"
>
<h1>
{{ detail.name }}
<span class="location">{{ detail.cars.location }}</span>
</h1>
<!-- vehicle details -->
<SingleGarage :detail="detail"> </SingleGarage>
</div>
<b-sidebar
id="my-sidebar"
title="Sidebar with backdrop"
backdrop-variant="dark"
ref="mySidebar"
backdrop
shadow
#emitData="testingEmit()"
>
<div class="px-3 py-2">
<h1>{{currentCar}}</h1>
</div>
</b-sidebar>
</div>
</template>
<script>
// # is an alias to /src
import axios from "axios";
import SingleGarage from "../components/SingleGarage";
export default {
components: { SingleGarage },
name: "Home",
data: () => ({
details: String,
currentCar: 'String',
}),
methods:{
testingEmit(data){
this.currentCar = data
console.log('data from emit',data)
}
},
mounted() {
axios
.get("https://api.jsonbin.io/b/5ebe673947a2266b1478d892")
.then((response) => {
var results;
response.data.forEach((element) => {
element.cars.vehicles.sort((a, b) => {
a = new Date(a.date_added);
b = new Date(b.date_added);
results = a > b ? -1 : a < b ? 1 : 0;
return results * -1;
});
});
this.details = response.data;
});
},
};
</script>
<template>
<div class="vGrid mt-4">
<div
class="gridItem border vehicle singleCar"
v-for="(vehicle, vehicleIndex) in detail.cars.vehicles"
:class="'griditem' + vehicleIndex"
:key="vehicle._id"
>
<SingleCar
:vehicle="vehicle"
#click.native="testingTef(vehicleIndex)"
></SingleCar>
</div>
</div>
</template>
<script>
import SingleCar from "#/components/SingleCar";
export default {
name: "SingleGarage",
components: { SingleCar },
props: ["detail"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
currentCar : 1
}),
methods: {
testingTef(vehicleIndex) {
this.$parent.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
this.$root.$emit('bv::toggle::collapse', 'my-sidebar')
console.log(this.detail.cars.vehicles[vehicleIndex].make)
console.log(this.detail.cars.vehicles[vehicleIndex].date_added)
this.currentCar = this.detail.cars.vehicles[vehicleIndex].make;
},
},
};
</script>
<template>
<div class="singleCar">
<!-- conditionally show image -->
<img
class="carImg"
:src="vehicle.img"
v-if="vehicle.img"
alt="No Preview"
/>
<img class="carImg" :src="dummyImg" v-else alt="No Preview" />
<div class="p-3">
<h3 class="make">{{ vehicle.make }}</h3>
<div class="modelDetails">
<div class="model d-flex ">
<p class="bold">Model:</p>
<p class="price ml-auto ">{{ vehicle.model }}</p>
</div>
<div class="price d-flex ">
<p class="bold">Price:</p>
<p class="price ml-auto ">€{{ vehicle.price }}</p>
</div>
</div>
<p class="dateAdded ml-auto ">{{ vehicle.date_added }}</p>
</div>
</div>
</template>
<script>
export default {
name: "SingleCar",
props: ["vehicle"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
}),
methods:{
working(){
console.log('working');
console.log(this.vehicle.make)
}
}
};
</script>
Thanks for your help.
So a few things you can try to fix this
in your Home.vue you can change
#emitData="testingEmit()"
to
#emitData="testingEmit"
// or
#emitData="testingEmit($event)"
You are telling to the function testingEmit that is not params to parse. So you need to take out the () and Vue will parse everything that comes from the $event or you cant say put the $event as a param in your testingEmit (second option).
For your SingleGarage.vue you can take the $parent.$emit and replace it with
this.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)

Vue modal with a router

I am new to Vue. I am building a simple app that will list all countries and when you click on a particular country it shows you more details about the country. Idea is to open country details in a modal.
I'm stuck with displaying that modal. The modal opens, but in the background. It also opens a detail page.
CountryDetail.vue:
<script>
import axios from 'axios';
export default {
name: 'country-detail',
props: [ 'isDarkTheme' ],
data () {
return {
pending: false,
error: null,
countryInfo: null,
alpha3Code: [],
alpha3CodetoString: [],
}
},
mounted () {
this.pending = true;
axios
.get(`https://restcountries.eu/rest/v2/name/${this.$route.params.country}?fullText=true`)
.then((response) => {
(this.countryInfo = response.data)
this.alpha3CodetoString = this.alpha3Code.join(';');
})
.catch(error => (this.error = error ))
.finally( () => { this.pending = false });
},
filters: {
formatNumbers (value) {
return `${value.toLocaleString()}`
}
}
}
</script>
<template>
<modal v-model="show">
<div class="modal-mask" :class="{ darkTheme : isDarkTheme }" name="modal">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
<h1 v-if="error !== null">Sorry, an error has occurred {{error}}</h1>
<div class="loaderFlex"><div v-if="pending" class="loader"></div></div>
</slot>
</div>
<div v-for="country in countryInfo" class="countryTile modal-body" v-bind:key="country.id">
<slot name="body">
<img v-bind:src="country.flag" alt="Country Flag" class="flag">
<div class="country-details">
<h1>{{country.name}}</h1>
<div class="listDiv">
<ul>
<li><span>Population:</span> {{country.population | formatNumbers }}</li>
<li><span>Capital:</span> {{country.capital}}</li>
<li><span>Iso:</span> {{country.alpha3Code}}</li>
</ul>
<ul>
<li><span>Currencies:</span> {{country.currencies['0'].name}}</li>
<li><span>Languages:</span>
<span
v-for="(language, index) in country.languages"
v-bind:key="index"
class="languages">
{{language.name}}<span v-if="index + 1 < country.languages.length">, </span>
</span>
</li>
</ul>
</div>
</div>
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<a #click="$router.go(-1)" class="backBtn"><i class="fas fa-arrow-left" />Go Back</a>
</slot>
</div>
</div>
</div>
</div>
</modal>
</template>
Home.vue:
<script>
import axios from 'axios';
export default {
name: 'home',
props: [ 'isDarkTheme' ],
data () {
return {
pending: false,
error: null,
countryInfo: null,
search: '',
darkMode: false,
}
},
mounted () {
this.pending = true;
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => (this.countryInfo = response.data))
.catch(error => (this.error = error ))
.finally( () => { this.pending = false });
},
filters: {
formatNumbers (value) {
return `${value.toLocaleString()}`
}
},
computed: {
filteredCountries: function () {
return this.countryInfo.filter((country) => {
if (this.region === '' ) {
return country.name.toLowerCase().match(this.search.toLowerCase());
} else if (this.search !== '') {
return country.name.toLowerCase().match(this.search.toLowerCase());
} else {
return ('blbla');
}
})
}
},
}
</script>
<template>
<div class="home" :class="{ darkTheme : isDarkTheme }">
<div class="searchBar">
<div class="searchContainer">
<i class="fas fa-search searchIcon"></i>
<input
class="searchInput"
type="text"
v-model="search"
aria-label="Search for a country..."
placeholder="Search for a country..."
/>
<ul class="searchResults"></ul>
</div>
</div>
<h1 v-if="error !== null">Sorry, an error has occurred {{error}}</h1>
<div class="loaderFlex"><div v-if="pending" class="loader"></div></div>
<div v-if="countryInfo" class="tileGrid" #click="showModal = true">
<div v-for="country in filteredCountries" class="countryTile" v-bind:key="country.id">
<router-link
:to="{ name: 'country-detail', params: {country: country.name }}"
class="linkTile"
>
<img v-bind:src="country.flag" alt="Country Flag" class="flag">
<div class="text">
<h1>{{ country.name }}</h1>
</div>
</router-link>
</div>
</div>
</div>
</template>
The router-link will always redirect you to another page, because its basically <a href="..."> see here. You don't need router if you just want to show the detail on a modal, you could just add the modal component inside the Home.vue component, then bind the modal and the countryName with props, then pass them in when clicking a button.
Home.vue:
<template>
<div>
<button #click="showDetail">
Show Detail
</button>
<CountryDetail :countryName="countryName" :showModal="showModal"/>
<div>
</template>
<script>
import CountryDetail from './CountryDetail.vue'
export default {
name: 'Home',
components: { CountryDetail },
data: () => ({
countryName: '',
showModal: false,
}),
methods: {
showDetail() {
this.showModal = true;
},
},
}
</script>
And instead of making request on mounted, you could use watch to do something like watching for the showModal prop, and make request everytime it has a truthy value. Like this:
CountryDetail.vue:
<template>
<modal v-model="showModal">
<!-- modal content -->
</modal>
</template>
<script>
export default {
name: 'CountryDetail',
props: ['countryName', 'showModal'],
watch: {
'showModal': {
deep: true,
handler(val) {
if (val && this.countryName !== '') {
// Make request
}
}
}
}
}
</script>

Popup does not open in App.vue when clicking on a search result

My goal is when clicking on any search item to open the Popup.
Why does not work — this.$emit('openPopup', bookId); in the method selectBook(bookId)
There is a component of Results.vue, which displays search results by using Google Books API:
<template>
<div class="results">
<ul class="results-items">
<li
class="book"
#click="selectBook(result.id)"
>
<img
:src="'http://books.google.com/books/content?id=' + result.id + '&printsec=frontcover&img=1&zoom=1&source=gbs_api'"
class="cover"
>
<div class="item-info">
<div class="bTitle">{{ result.volumeInfo.title }}</div>
<div
class="bAutors"
v-if="result.volumeInfo.authors"
>
{{ result.volumeInfo.authors[0] }}
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['result'],
data() {
return {
bookId: '',
};
},
methods: {
selectBook(bookId) {
this.$router.push(`bookId${bookId}`);
this.$store.dispatch('selectBook', bookId);
this.$emit('hideElements', bookId);
this.$emit('openPopup', bookId);
},
},
};
</script>
Rusults screenshot
Is the main App.vue, which I want to display Popup:
<template>
<div id="app">
<div class="container">
<Header />
<popup
v-if="isOpenPopup"
#closePopup="closeInfoPopup"
#openPopup="showModal"
/>
<router-view/>
</div>
</div>
</template>
<script>
import Header from '#/components/Header.vue';
import Popup from '#/components/Popup.vue';
import 'bootstrap/dist/css/bootstrap.css';
export default {
components: {
Header,
Popup,
},
data() {
return {
isOpenPopup: false,
};
},
methods: {
showModal() {
console.log('click');
this.isOpenPopup = true;
},
closeInfoPopup() {
this.isOpenPopup = false;
},
},
};
</script>
The component Popup.vue
<template>
<div class="popup">
<div class="popup_header">
<span>Popup name</span>
</div>
<div class="popup_content">
<h1>Hi</h1>
<slot></slot>
<button #click="closePopup">Close</button>
</div>
</div>
</template>
<script>
export default {
name: 'popup',
props: {},
data() {
return {};
},
methods: {
closePopup() {
this.$emit('closePopup');
},
},
};
</script>
You are listening on popup component but triggering events on Result
Don't remember that events are bound to component.
You have several options how to handle it
Add event listeners (like #openPopup) only on Result component and use portal-vue library to render popup on top level
use global events, this.$root.emit and listen also on this.$root. In this case you add event listener in mount() hook and don't forget unregister it in beforeDestroy() hook
you can use Vuex and popup visibility and related data in global application store provided by Vuex

Vue.js: show profile image in laravel public folder

I am creating commenting system.
And I integrated vue.js in my laravel project.
In my comment area, I want to show user profile image in my laravel
public folder.
But I have no idea how to show image.
What I want to achieve is,
if user has a profile image, I want to show it in comment area. But if
user doesn't have a profile image, I want to show an avatar.
I used vue avatar component so I think I want to use it.
My profile images are stored in public/uploads/profile.
comment.vue
<template>
<div>
<div class="reply-comment" >
<div class="user-comment">
<div class="user">
<!---<img src="{{ $comment->user->img }}" class="image-preview__image">--->
<avatar :username="comment.user.name" :size="45"></avatar>
</div>
<div class="user-name">
<span class="comment-name"><a :href=" '/user/' + 'profile' +'/'+ comment.user.id + '/' ">{{ comment.user.name }}</a></span>
<p>{{ comment.body }}</p>
</div>
</div>
</div>
<div class="reply">
<button #click="addingReply = !addingReply" class="reply-button" :class="{ 'red' : !addingReply, 'black' : addingReply }">
{{ addingReply ? 'Cancel' : 'Add Reply'}}
</button>
</div>
<div class="user-reply-area" v-if="addingReply">
<div class="reply-comment">
<input v-model='body' type="text">
</div>
<button #click="addReply" class="comment-button"><span>Add Reply</span></button>
</div>
<replies ref='replies' :comment="comment"></replies>
</div>
</template>
<script>
import Avatar from 'vue-avatar'
import Replies from './replies.vue'
export default {
components: {
Avatar,
Replies
},
data() {
return {
body: '',
addingReply: false
}
},
props: {
comment: {
required: true,
default: () => ({})
},
post: {
required: true,
default: () => ({})
}
},
methods: {
addReply() {
if(! this.body) return
axios.post(`/comments/${this.post.id}`, {
comment_id: this.comment.id,
body: this.body
}).then(({data})=> {
this.body = ''
this.addingReply = false
this.$refs.replies.addReply(data)
})
.catch(function (error) {
console.log(error.response);
});
}
}
}
</script>
profile.php
public function user(){
return $this->belongsTo(User::class, 'user_id');
}
user.php
public function profile(){
return $this->hasOne(Profile::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
comment.php
protected $with = ['user'];
protected $appends = ['repliesCount'];
web.php
Route::get('results/{post}/comments', 'CommentController#index');
If your database column name for the image file is img then
you can do it like below:
<div class="user" v-if="comment.user.img">
<img :src="'uploads/profile/'+comment.user.img">
</div>
<div class="user" else>
<img :src="default_image_path_here">
</div>
If you are using multiple images for a user you need to add a loop as follow:
<div class="user" v-if="comment.user.img.length > 0">
<span v-for="(item, index) in comment.user.img">
<img :src="'uploads/profile/'+item.your_image_name_column">
</span>
</div>
<div class="user" else>
<img :src="default_image_path_here">
</div>

Vue.js: List does not update when using child component

I'm having an issue when using a child component, a list does not update based on a prop passed into it.
If the comments array data changes, the list will not update when it uses a child component <comment></comment>.
TopHeader template:
<template>
<ul v-for="comment in comments">
// If don't use a child component, it updates whenever `comments` array changes:
<li>
<div>
/r/{{comment.data.subreddit}} ·
{{comment.data.score}}
</div>
<div class="comment" v-html="comment.data.body"></div>
<hr>
</li>
</ul>
</template>
TopHeader component:
import Comment from 'components/Comment'
export default {
name: 'top-header',
components: {
Comment
},
data () {
return {
username: '',
comments: []
}
},
methods: {
fetchData: function(username){
var vm = this;
this.$http.get(`https://www.reddit.com/user/${username}/comments.json?jsonp=`)
.then(function(response){
vm.$set(vm, 'comments', response.body.data.children);
});
}
}
}
However, if I use a child component it does not update.
Modified TopHeader template:
<template>
<ul v-for="comment in comments">
// If I instead use a component with prop data, it does not update
<comment :data="comment.data"></comment>
</ul>
</template>
Comment child template:
<template>
<li>
<div>
/r/{{subreddit}} ·
{{score}}
</div>
<div class="comment" v-html="body"></div>
<hr>
</li>
</template>
Comment child component:
export default {
name: 'comment',
props: ['data'],
data() {
return {
body: this.data.body,
subreddit: this.data.subreddit,
score: this.data.score,
}
}
}
Move the v-for statement to the component <comment>. With the v-for on the ul-tag you would repeat the ul tag. See http://codepen.io/tuelsch/pen/YNOqYR again for an example.
<div id="app">
<button v-on:click="fetch">Fetch data</button>
<ul>
<comment v-for="comment in comments" v-bind:comment="comment" />
</ul>
</div>

Categories