VueJS + Rest API list rendering issue - vue.js

I have a Spring Data Rest backend and in src/main/resources/static html + js assets which work fine. My issue is that I can't understand how to render the data picked up from the webservice in the interface.
In case I set the data explicitly as an array, it works fine (see https://v2.vuejs.org/v2/guide/list.html).
Thank you in advance!
...
const ListFormsApi = {
template: '<div><ul><li v-for=\'item in items\'>{{item.details.Title}}</li></ul></div>',
data: function(){
return {
items: ''
}
},
created: function() {
this.get()
},
methods: {
get: function() {
axiosInstance.get('/contactTemplate')
.then(function (response) {
this.items = response.data._embedded.contactTemplate
}).catch(function (error) {
console.log(error)
}
);
}
}
}
...
The webpage is quite simple and straightforward from documentation examples (assume that complete html and head tags are present as well...
<body>
<div id="app">
<h1><router-link to="/">ContactForm Factory!</router-link></h1>
<p>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
<router-link to="/listForms">List Forms</router-link>
<router-link to="/listFormsApi">List Forms API</router-link>
</p>
<router-view></router-view>
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router#2.0.0/dist/vue-router.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="app.js"></script>
</body>

I think this is happening because scope of this is not what you are expecting inside the then block of axiosInstance, you need to make following change to make it work.
methods: {
get: function() {
var self = this
axiosInstance.get('/contactTemplate')
.then(function (response) {
self.items = response.data._embedded.contactTemplate
}).catch(function (error) {
console.log(error)
}
);
}
You can have a look at my answer on the similar problem here.

When you are registering the .then() callback the context of the function changes.
In order to keep the context you can use the bind method.
methods: {
get: function() {
axiosInstance.get('/contactTemplate')
.then(function (response) {
this.items = response.data._embedded.contactTemplate
}.bind(this)) // added .bind
.catch(function (error) {
console.log(error)
});
}
}

vue-resource is now using response.body rather than data so look to update as follows:
methods: {
get: function() {
axiosInstance
.get('/contactTemplate')
.then((response) => {
this.$set(this.$data, 'items', response.body._embedded.contactTemplate)
})
.catch((error) => {
console.log(error)
});
}
}
I've also used arrow syntax to correct the scope of this and this.$set to ensure the data that you set is reactive.
If this still doesn't produce the desired result I'd confirm the data is correctly returning from the endpoint. Vue-resource has methods such as response.json() available if for instance the server responds with an incorrect content-type.

Related

Update a value in an axios URL in VueJS

I am trying to update 'this.counts' value in my axios called URL when a button is clicked. Can anyone help me to work this out? Thanks
.get(
"https://myurl/results.json" +
query +
`&num_ranks=$**{this.counts}**`
data: function () {
return {
counts: 2
};
methods: {
//number added to the Axios URL to display load more results
loadMore() {
if (this.loadMore) {
this.counts += 10;
}
},
}
<button
#click="loadMore">
Load more {{ counts }}
</button>
One thing I found missing is, that on the counts variable's update, the Axios API should be re-triggered to fetch the new response. And if your re-triggering it then it should work. You might need to check your API then.
Here is a dummy API demo that is taking counts as the query param and fetching the response every time on the counts variable's update.
Vue.config.productionTip = false;
var app = new Vue({
el: '#app',
data() {
return {
counts: 0
}
},
methods: {
loadMore() {
if (this.loadMore) {
this.counts += 1;
this.getMore()
}
},
getMore() {
console.clear()
// Axios call here
axios.get(`https://jsonplaceholder.typicode.com/comments?postId=${this.counts}`).then(res => {
console.log(res)
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.3.2/axios.min.js" integrity="sha512-NCiXRSV460cHD9ClGDrTbTaw0muWUBf/zB/yLzJavRsPNUl9ODkUVmUHsZtKu17XknhsGlmyVoJxLg/ZQQEeGA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="loadMore">Load more {{ counts }}</button>
</div>
What you need is using params in your axios call.
axios.get('https://myurl/results.json', { params: { num_ranks: this.counts } });
parameters will append to the url your are calling.
If you have more parameters just put them in params: {...}
axios.get('https://myurl/results.json', {
params: {
param1: this.result,
param2: this.result2,
param3: this.result3
}
});
Create a specific function for axios API request and call it whenever the button clicked. (Call it in loadMore().)
<template>
<button #click="loadMore">Load more {{ counts }}</button>
</template>
<script>
export default {
data() {
return {
counts: 0
}
},
methods: {
loadMore() {
if (this.loadMore) {
this.counts += 10;
this.getMore()
}
},
getMore() {
// Axios call here
axios.get("https://myurl/results.json" + query + `&num_ranks=$**{this.counts}**`)
}
}
}
</script>
You can use URLSearchParams to get the url param by name and modify in your code. Something like this:
const url = window.location.href // get the url from current page
const searhParams = new URLSearchParams(url);
if (searchParams.has('name of the param in url')) {
let count = searchParams.get('param in url');
loadMore(count); // you pass as a param to loadMore function or do it inside that function
}
Here's the doc for more help: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams

Vue 3 display fetch data v-for

So, I'm creating a Pokemon application and I would like to display the pokemon names using the api : https://pokeapi.co/api/v2/pokemon/.
I'm doing a fetch request on the api and then display the pokemon names in my template. I have 0 problem when I try to display only 1 pokemon but I have this error when I try to display all my pokemons using v-for.
Do you have any idea why I meet this error ?
<template>
<p class="dark:text-white"> {{pokemons[0].name}} </p> //working
<div v-for="(pokemon, index) in pokemons" :key="'poke'+index"> //not working...
{{ pokemon.name }}
</div>
</template>
<script>
const apiURL = "https://pokeapi.co/api/v2/pokemon/"
export default {
data(){
return{
nextURL:"",
pokemons: [],
};
},
created(){
this.fetchPokemons();
},
methods:{
fetchPokemons(){
fetch(apiURL)
.then( (resp) => {
if(resp.status === 200){
return resp.json();
}
})
.then( (data) => {
console.log(data.results)
// data.results.forEach(pokemon => {
// this.pokemons.push(pokemon)
// });
// this.nextURL = data.next;
this.pokemons = data.results;
console.log(this.pokemons);
})
.catch( (error) => {
console.log(error);
})
}
}
}
</script>
<style>
</style>
I've just pasted your code into a Code Pen and removed the working/not working comments and the code runs and shows the names.
Maybe the problem is in the parent component where this component is mounted, or the assignment of the :key attribute
try :key="'poke'+index.toString()", but I'm pretty sure js handels string integer concats quiet well.
Which version of vuejs do you use?
Edit from comments:
The parent component with the name PokemonListVue imported the posted component as PokemonListVue which resulted in a naming conflict. Renaming either one of those solves the issue.
In the error message posted, in line 3 it says at formatComponentName this is a good hint.

How can I add a click event to a class in Vue **without** changing the html? [duplicate]

Is there a way to add a click handler inside a Vue instance without writing on the markup? I'm loading an SVG via ajax, and I'd like to use Vue click events on it.
My Vue file looks like this:
<template>
<div>
<div class="map" v-html="map"></div>
</div>
</template>
<script>
export default{
data : function(){
return {
map : 'Loading'
};
},
methods : {
getMap : function(){
var thisMap = this;
axios.get('/img/world-map.svg').then(function (response) {
thisMap.map = response.data;
thisMap.loading = false;
}).catch(function (error) {
thisMap.loading = false;
console.log(error);
});
},
},
mounted : function(){
console.log('WorldMap mounted');
this.getMap();
}
}
</script>
The issue is that I'm loading a rather large SVG straight into the page. In a perfect world I would just do something like this:
<template>
<div>
<div class="map" >
<svg>
<g #click="groupClicked" class="asia" id="asia" xmlns:svg="http://www.w3.org/2000/svg">
<path stroke="#FFFFFF" d="M715.817,47.266c-4.45,1.25-8.903,2.497-13.357,3.739c-1.074,0.327-8.403,1.757-5.678,3.204
c-1.922,2.104-2.993,1.568-5.547,1.536c-1.547,1.333,0.981,1.22-0.558,2.421c-0.976,0.761-0.946,1.257-2.106,0.827
c-0.368-0.136-2.223-0.261-1.543,0.759c2.082,1.3,0.231,3.046-1.466,4.011c-1.831-0.38-3.271-1.611-5.245-1.293
c-1.229,0.196-2.104,0.763-3.176-0.205c-1.265-1.143,0.371-1.409,1.378-2.177c1.529-1.168,5.473-0.2,2.834-2.668
c1.061-0.979,2.07-0.946,3.206-1.736c-0.297-0.416-0.649-0.773-1.067-1.068c1.047-1.075,1.679-3.036,3.497-2.725
c1.441,0.249,2.046-1.318,3.182-2.137c1.121-0.811,2.4-1.266,3.771-1.402c1.656-0.165,3.271,0.134,4.347-1.427
c0.921-1.334,1.921-1.218,3.468-0.757c1.687,0.504,2.808-0.159,4.442-0.698c2.313-0.118,4.489-0.946,6.812-1.068
c1.043-1.941,2.354-2.07,4.375-2.331c0.653-0.085,6.433-0.678,4.774,1.792C721.041,46.198,718.024,46.605,715.817,47.266
C711.364,48.516,718.356,46.505,715.817,47.266z"/>
</g>
</svg>
</div>
</div>
</template>
However the SVG I'm loading is about 300kb big and I don't want to be carrying that around with all my JavaScript on every page load.
Any comments or solutions welcome.
Edit
Since asking I've actually got quite far with this approach, which isn't perfect but at the moment it seems pretty good.
var vueBus = new Vue({});
$('body').on('click', 'svg g', function(){
var name = $(this).attr('data-name');
vueBus.$emit('svgGroupClicked', name);
});
and then adding a listener in my .vue file
mounted() : function(){
vueBus.$on('svgGroupClicked', function(){ ... });
}
You can just add a click event via plain javascript after the GET request succeeds:
axios.get('/img/world-map.svg').then(function (response) {
thisMap.map = response.data;
thisMap.addClickHandler();
thisMap.loading = false;
})
Your addClickHandler method would look something like this:
methods: {
addClickHandler: function() {
var gEl = this.$el.getElementsByTagName('g')[0];
gEl.addEventListener('click', function() {
alert('I got clicked');
});
},
}
Or, if you're using jQuery:
methods: {
addClickHandler: function() {
$(this.$el).find('g')[0].click(function() {
alert('I got clicked');
});
},
}

Dynamic list not rendered

I'm a beginner in VueJS so any help would be appreciated.
So I'm trying to display a list from my API. My API is working fine as I have tested it in Postman but when I try to create a list based on the response of my API, the list is not displayed. What am I doing wrong?
Here is my html:
<div id="tabs">
<ul>
<li v-for="user in users">
{{ user.userid }}
</li>
</ul>
</div>
And here is my js:
var tabs = new Vue({
el: '#tabs',
data: {
users: []
},
mounted: function(){
this.getAllUsers()
},
methods: {
getAllUsers: function(){
axios.get('<?php echo base_url(); ?>users/all_users')
.then(function(response){
this.users = response.data
console.log(this.users)
})
}
}
})
Here is the screenshot of the console, the data are just for testing.
value of users after getting response from API
In your axios "then" method, you write:
.then(function(response){
this.users = response.data;
});
When using function keyword to declare a function, it creates its own context and therefore this value of its parent scope is not passed down to the function.
In Vue's case, this in the child function is not the intended Vue instance.
To solve this, simply pass the parent's this value to the callback, or use arrow functions.
Passing down using .bind
.then(function(response){
this.users = response.data;
}.bind(this));
Using arrow function syntax
.then(response => {
this.users = response.data;
});
<?php echo base_url(); ?>
I don't think this will be rendered in a js file. try hardcoding your baseURL instead.
Example:
getAllUsers: function(){
axios.get('http://app.test/users/all_users') // or axios.get('/api/users/all_users') if you're using an api based route for your api
.then(function(response) {
this.users = response.data
console.log(this.users)
})
}

Dynamic v-for rendering from http get results to Vue component

I'm trying to achieve a dynamic rendering of elements base from the return items array of the target API endpoint. Below is what I've tried
<template id="list-comp-tpl">
<button #click="searchNow">Search Now</button>
<ul v-for="item in searchResults">
<li>{{ item.name }}</li>
</ul>
</template>
<div id="app">
<list-comp></list-comp>
</div>
Vue.components('list-comp',{
template : '#list-comp-tpl',
data() {
return {
searchResults : [];
}
},
method : {
searchNow(){
// perform api query
axios.get('http://apiwebsite.com/search')
.then(function (response) {
// handle success
this.searchResults = response.data.msg;
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
}
}
});
new Vue({
el '#app'
});
But the component list-comp is not updating at all like when there's a return data from the api, it does not display as I expect it to. What can I try next?
for me "this" is not visible within function(response)
solutions that work for me
.then(response => this.searchResults = response.data.msg)
or, If you are keen on function(response) try this:
searchNow(){
_self = this;
// perform api query
axios.get('http://apiwebsite.com/search')
.then(function (response) {
// handle success
_self.searchResults = response.data.msg;
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
}
Try assigning values as shown.
this.searchResults = Object.assign({}, response.data.msg);
The way you are setting the values in array they are not set as reactive property on the DOM. Hence your component is not updated with new data.
You can also use Vue.set for updating array values.
Refer below link.
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats